/*
 * Copyright 2011 PA Consulting Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.prodeagle.java.counters;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import com.prodeagle.java.MemCacheManager;

/**
 * A Counter - for storing between 1 and many counters and their values into MemCache.
 * 
 * Use as follows:
 * 
 * <code>Counter.increment("CounterA"); //increments CounterA by 1</code>
 * or
 * <code>Counter.increment("CounterA", 5L); //increments CounterA by 5</code>
 * @author Edward Hartwell Goose
 *
 */
public class Counter {

	private static final Logger _logger = Logger.getLogger(Counter.class.getName());
	
	private static final Boolean SAVE_PRODEAGLE_STATS = true;
	
	/**
	 * Increment (and create if necessary) one named counter by a value of 1
	 * @param name - the name of the counter
	 */
	public static void increment(String name) {
		Counter.increment(name, 1L);
	}
	
	/**
	 * Increment (and create if necessary) one named counter by a value of delta
	 * @param name - the name of the counter
	 * @param delta - the value to increment the counter by (can be negative)
	 */
	public static void increment(String name, long delta) {
		Counter.increment(name, delta, SAVE_PRODEAGLE_STATS);
	}
	
	/**
	 * Increment (and create if necessary) one named counter by a value of delta 
	 * @param name - the name of the counter
	 * @param delta - the value to increment by
	 * @param saveProdEagleStats - whether to increment the ProdEagle stats counters
	 */
	public static void increment(String name, long delta, Boolean saveProdEagleStats) {
		incrementBatch(Collections.singletonMap(name, delta), saveProdEagleStats);
	}

	/**
	 * Increment a batch of counters
	 * @param counters - the map of counters and values to increment by
	 * @param saveProdeagleStats - whether to record ProdEagleStats or not
	 */
	protected static void incrementBatch(Map<String, Long> counters, Boolean saveProdeagleStats) {
		try {
			CounterNamesManager cnm = CounterUtil.getDefaultCounterNamesManager();
			long slot = CounterUtil.getEpochRounded(); //the current slot we're in
			
			//store the counters and return their new values
			Future<Map<String, Long>> storedCountersFuture = MemCacheManager.storeMultipleCounters(counters, CounterUtil.NAMESPACE, String.valueOf(slot), 0);
			
			List<String> counterNames = new ArrayList<String>();
			
			Map<String, Long> storedCounters = storedCountersFuture.get(10, TimeUnit.SECONDS);
			
			for (String name : counters.keySet()) {
				/*
				 * When saving counters in memcache, we save them with the key: "slotvalue"_"countername", 
				 * so use this when comparing the counters.
				 * 
				 * If the values of the counters are the same, the value 
				 */
				if (counters.get(name).equals(storedCounters.get(slot + "_" + name))) {
					counterNames.add(name);
				}
			}
			
			/*
			 * Object[] is expected to be of the form { Boolean, Long }, where the Boolean indicates if reading
			 * the database was required. The Long indicates the number of new counters added 
			 */
			Object[] addIfNewResult = cnm.addIfNew(counters.keySet());
			
			/*
			 * ProdEagle stores some counters for itself to see how it's being used.
			 * If the data store was accessed (read) or written to, these stats are recorded
			 * 
			 * When these stats are saved, they in themselves generate more stats. So we force a "false" in
			 * the commit to prevent an infinite loop
			 */
			if (saveProdeagleStats && (null != addIfNewResult[0] || null != addIfNewResult[1])) {
				BatchCounter batch = new BatchCounter();
				
				if (null != addIfNewResult[0]) {
					Boolean readAccess = (Boolean) addIfNewResult[0]; //equivalent to data_store_access in counter.py.
					if (readAccess) {  //Checks if we had to read from the datastore
						batch.increment("ProdEagle.Datastore.ReadAccess");
					}
				}
					
				if (null != addIfNewResult[1]) {
					Long newCountersCount = (Long) addIfNewResult[1]; //equivalent to n_added_names in counter.py
					if (newCountersCount > 0L) { //Records if we added any new counters (and therefore wrote to datastore)
						batch.increment("ProdEagle.NewNames", newCountersCount);
						batch.increment("ProdEagle.Datastore.WriteAccess");
					}
				}
				
				batch.commit(false); //don't save the stats for saving these stats (otherwise we'll go on forever)
			}
		} catch (Exception e) {
			_logger.severe("Exception occured while incrementing the following counters: " + counters.keySet());
			_logger.severe("Exception was: " + e);
		}
	}
}